﻿namespace ImageProcessing
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    /// <summary>
    /// ResizeImage can apply different resizing algorithms on the opened image.
    /// </summary>
    public class ResizeImage
    {
        /// <summary>
        /// Gets the height.
        /// </summary>
        /// <param name="imageMatrix">The image matrix.</param>
        /// <returns>returns height</returns>
        public static int GetHeight(ImageStructure[,] imageMatrix)
        {
            return imageMatrix.GetLength(0);
        }

        /// <summary>
        /// Gets the width.
        /// </summary>
        /// <param name="imageMatrix">The image matrix.</param>
        /// <returns>returns width</returns>
        public static int GetWidth(ImageStructure[,] imageMatrix)
        {
            return imageMatrix.GetLength(1);
        }

        /// <summary>
        /// Resize with nearest neighbour algorithm.
        /// </summary>
        /// <param name="originalImage">The source img.</param>
        /// <param name="mappingMethod">True: direct mapping , false: reverse mapping</param>
        /// <param name="horizontalTarget">Tf direct mapping then horizontalTarget=verticalTarget = the factor to enlarge the image
        /// else each parameter will represent increase in a different axis.</param>
        /// <param name="verticalTarget">the increase in the vertical axis (y)/ height of the image.</param>
        /// <returns>
        /// The destination which differ in size.
        /// </returns>
        public static Bitmap NearestNeighborResize(Bitmap originalImage, bool mappingMethod, int horizontalTarget, int verticalTarget)
        {
            int originalheight = originalImage.Height; // 240
            int originalWidth = originalImage.Width;

            Bitmap bmpNew = new Bitmap(horizontalTarget, verticalTarget, PixelFormat.Format24bppRgb);
            unsafe
            {
                BitmapData bmpNewD = bmpNew.LockBits(new Rectangle(0, 0, horizontalTarget, verticalTarget), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                BitmapData bmpOldD = originalImage.LockBits(new Rectangle(0, 0, originalWidth, originalheight), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                int oldExtraBytes = bmpOldD.Stride - (originalWidth * 3);
                int newExtraBytes = bmpNewD.Stride - (horizontalTarget * 3);

                double widthRatio = (double)originalWidth / (double)horizontalTarget;
                double heightRatio = (double)originalheight / (double)verticalTarget;

                PHFastCpp.PHResizeNearestNeighbor((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, originalWidth, horizontalTarget, verticalTarget, oldExtraBytes, newExtraBytes, widthRatio, heightRatio);

                bmpNew.UnlockBits(bmpNewD);
                originalImage.UnlockBits(bmpOldD);
            }

            return bmpNew;
        }

        /// <summary>
        /// Bis the line resize.
        /// </summary>
        /// <param name="factorX">The factor X is new widgth.</param>
        /// <param name="factorY">The factor Y is new heigth.</param>
        /// <param name="myimage">The myimage array of old image .</param>
        /// <returns>
        /// nnew image array
        /// </returns>
        public static Bitmap BiLineResize(int factorX, int factorY, Bitmap myimage)
        {
            int height = myimage.Height;
            int width = myimage.Width;

            Bitmap bmpNew = new Bitmap(factorX, factorY, PixelFormat.Format24bppRgb);
            Bitmap bmpOld = myimage;
            unsafe
            {
                BitmapData bmpNewD = bmpNew.LockBits(new Rectangle(0, 0, factorX, factorY), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                BitmapData bmpOldD = bmpOld.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

                int oldExtraBytes = bmpOldD.Stride - (width * 3);
                int newExtraBytes = bmpNewD.Stride - (factorX * 3);

                double widthRatio = (double)width / (double)factorX;
                double heightRatio = (double)height / (double)factorY;

                PHFastCpp.PHResizeBillinear((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, width, height, factorX, factorY, oldExtraBytes, newExtraBytes, heightRatio, widthRatio);

                bmpNew.UnlockBits(bmpNewD);
                bmpOld.UnlockBits(bmpOldD);
            }

            return bmpNew;
        }

        /// <summary>
        /// Bicubics the resize.
        /// </summary>
        /// <param name="newX">The new X.</param>
        /// <param name="newY">The new Y.</param>
        /// <param name="image">The image.</param>
        /// <param name="mode">The mode.</param>
        /// <returns>
        /// Resized Image
        /// </returns>
        public static ImageStructure[,] BicubicResize(int newX, int newY, ImageStructure[,] image, int mode)
        {
            ImageStructure[,] newImage = new ImageStructure[newY, newX];
            int columnsPerThread = newY / Environment.ProcessorCount;
            int i;
            Thread t = null;
            Semaphore s = new Semaphore(1, 1000);
            int height = GetHeight(image);
            int width = GetWidth(image);
            List<Thread> threadList = new List<Thread>();
            for (i = 1; i < Environment.ProcessorCount; i += 0)
            {
                s.WaitOne();
                int minHeight = i * columnsPerThread;
                int maxHeight = (i + 1) * columnsPerThread;
                if (i == (Environment.ProcessorCount - 1))
                {
                    t = new Thread(delegate() { new ResizeImage().BicubicResizeThreading(s, minHeight, newY, image, newImage, height, width, mode); });
                    t.Start();
                    threadList.Add(t);
                    break;
                }

                t = new Thread(delegate() { new ResizeImage().BicubicResizeThreading(s, minHeight, maxHeight, image, newImage, height, width, mode); });
                threadList.Add(t);
                t.Start();
                i++;
            }

            new ResizeImage().BicubicResizeThreading(s, 0, columnsPerThread, image, newImage, height, width, mode);
            foreach (Thread thread in threadList)
            {
                thread.Join();
            }

            return newImage;
        }

        /// <summary>
        /// Bicubics the resize.
        /// </summary>
        /// <param name="newX">The new X.</param>
        /// <param name="newY">The new Y.</param>
        /// <param name="image">The image.</param>
        /// <param name="mode">The mode.</param>
        /// <returns>Resized Bitmap</returns>
        public static Bitmap BicubicResize(int newX, int newY, Bitmap image, int mode)
        {
            int height = image.Height;
            int width = image.Width;

            Bitmap bmpNew = new Bitmap(newX, newY, PixelFormat.Format24bppRgb);
            Bitmap bmpOld = image;
            unsafe
            {
                BitmapData bmpNewD = bmpNew.LockBits(new Rectangle(0, 0, newX, newY), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                BitmapData bmpOldD = bmpOld.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

                int oldExtraBytes = bmpOldD.Stride - (width * 3);
                int newExtraBytes = bmpNewD.Stride - (newX * 3);

                double widthRatio = (double)width / (double)newX;
                double heightRatio = (double)height / (double)newY;

                switch (mode)
                {
                    case 0:
                        PHFastCpp.PHResizeBiCubicTriangular((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, image.Width, image.Height, newX, newY, oldExtraBytes, newExtraBytes, heightRatio, widthRatio);
                        break;
                    case 1:
                        PHFastCpp.PHResizeBiCubicBell((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, image.Width, image.Height, newX, newY, oldExtraBytes, newExtraBytes, heightRatio, widthRatio);
                        break;
                    case 2:
                        PHFastCpp.PHResizeBiCubicBSpline((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, image.Width, image.Height, newX, newY, oldExtraBytes, newExtraBytes, heightRatio, widthRatio);
                        break;
                    case 3:
                        PHFastCpp.PHResizeBiCubicCatMullRom((int)bmpOldD.Scan0, (int)bmpNewD.Scan0, image.Width, image.Height, newX, newY, oldExtraBytes, newExtraBytes, heightRatio, widthRatio);
                        break;
                    default:
                        break;
                }

                bmpNew.UnlockBits(bmpNewD);
                bmpOld.UnlockBits(bmpOldD);
            }

            return bmpNew;
        }

        /// <summary>
        /// Bicubics the triangulaor.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="mode">The mode.</param>
        /// <returns>
        /// Calculated interpolated value
        /// </returns>
        private double BicubicInterpolationFunction(double value, int mode)
        {
            if (mode == 0)
            {
                // Triangular
                if (-1 <= value && value <= 0)
                {
                    return value + 1;
                }
                else if (0 < value && value <= 1)
                {
                    return 1 - value;
                }
                else
                {
                    return 0;
                }
            }
            else if (mode == 1)
            {
                // Bell
                value = (value / 2.0) * 1.5;
                if (-1.5 < value && value < -0.5)
                {
                    return 0.5 * Math.Pow((value + 1.5), 2);
                }
                else if (-0.5 < value && value <= 0.5)
                {
                    return 0.75 - (value * value);
                }
                else if (0.5 < value && value <= 1.5)
                {
                    return 0.5 * Math.Pow((value - 1.5), 2);
                }

                return 0;
            }
            else if (mode == 2)
            {
                // B Spline
                if (value < 0.0)
                {
                    return 0;
                }
                else
                {
                    return (1.0 / 6.0) * (Math.Pow((value + 2), 3) - (4 * Math.Pow((value + 1), 3)) + (6 * Math.Pow(value, 3)) - (4 * Math.Pow((value - 1), 3)));
                }
            }
            else if (mode == 3)
            {
                // Cat Mull Rom
                const double B = 0.8;
                const double C = 0.2;
                if (value < 0.0)
                {
                    value = -value;
                }

                if (value < 1.0)
                {
                    return ((12 - (9 * B) - (6 * C)) * Math.Pow(value, 3)) + ((-18 + (12 * B) + (6 * C)) * Math.Pow(value, 2)) + 6 - (2 * B);
                }
                else if (value >= 1.0 && value < 2.0)
                {
                    return ((-B - (6 * C)) * Math.Pow(value, 3)) + (((6 * B) + (30 * C)) * Math.Pow(value, 2)) + (((-12 * B) - (48 * C)) * value) + (8 * B) + (24 * C);
                }
                else
                {
                    return 0.0;
                }
            }

            return 0.0;
        }

        /// <summary>
        /// Nearests the neighbor reversethread.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minColumnIndex">Index of the min column.</param>
        /// <param name="maxColumnIndex">Index of the max column.</param>
        /// <param name="originalImage">The original image.</param>
        /// <param name="targetImg">The target img.</param>
        /// <param name="widthRatio">The width ratio.</param>
        /// <param name="heightRatio">The height ratio.</param>
        private void NearestNeighborReversethread(Semaphore s, int minColumnIndex, int maxColumnIndex, ImageStructure[,] originalImage, ImageStructure[,] targetImg, float widthRatio, float heightRatio)
        {
            s.Release();
            for (int destinationColumnIndex = minColumnIndex; destinationColumnIndex < maxColumnIndex; destinationColumnIndex++)
            {
                for (int destinationRawIndex = 0; destinationRawIndex < targetImg.GetLength(0); destinationRawIndex++)
                {
                    double fractionalOriginalX = destinationColumnIndex * widthRatio;
                    double fractionalOriginalY = destinationRawIndex * heightRatio;
                    int oldX = Convert.ToInt32(Math.Truncate(fractionalOriginalX));
                    int oldY = Convert.ToInt32(Math.Truncate(fractionalOriginalY));
                    targetImg[destinationRawIndex, destinationColumnIndex] = originalImage[oldY, oldX];
                }
            }
        }

        /// <summary>
        /// Nearests the neighbor resizethread.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minWidth">Width of the min.</param>
        /// <param name="maxWidth">Width of the max.</param>
        /// <param name="originalImage">The original image.</param>
        /// <param name="targetImg">The target img.</param>
        /// <param name="factor">The factor.</param>
        private void NearestNeighborResizethread(Semaphore s, int minWidth, int maxWidth, ImageStructure[,] originalImage, ImageStructure[,] targetImg, int factor)
        {
            s.Release();
            for (int srcColumnIndx = minWidth; srcColumnIndx < maxWidth; srcColumnIndx++)
            {
                for (int srcRawIndx = 0; srcRawIndx < originalImage.GetLength(0); srcRawIndx++)
                {
                    // filling the destination from the source
                    // taking one element from the source and filling a matrix, from the destination of size factorXfactor with this const element
                    for (int cc = srcColumnIndx * factor; cc < (srcColumnIndx * factor) + factor; cc++)
                    {
                        for (int rr = srcRawIndx * factor; rr < (srcRawIndx * factor) + factor; rr++)
                        {
                            targetImg[rr, cc] = originalImage[srcRawIndx, srcColumnIndx];
                        }
                    } // one colomn from destination image is set each loop
                }
            }
        }

        /// <summary>
        /// Bicubics the resize threading.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="image">The image.</param>
        /// <param name="newImage">The new image.</param>
        /// <param name="height">The height.</param>
        /// <param name="width">The width.</param>
        /// <param name="mode">The mode.</param>
        private void BicubicResizeThreading(Semaphore s, int minHeight, int maxHeight, ImageStructure[,] image, ImageStructure[,] newImage, int height, int width, int mode)
        {
            s.Release();
            ResizeImage instance = new ResizeImage();
            int newWidth = GetWidth(newImage);
            double smallwRatio = (double)width / (double)newWidth;
            double heightRatio = (double)height / (double)GetHeight(newImage);
            double dy = 0;
            double dx = 0;
            double sumR, sumG, sumB, sumDenom, p, q;
            for (int i = minHeight; i < maxHeight; i++)
            {
                for (int j = 0; j < newWidth; j++)
                {
                    sumR = 0;
                    sumG = 0;
                    sumB = 0;
                    sumDenom = 0;
                    p = (double)i * heightRatio;
                    q = (double)j * smallwRatio;
                    dy = p - Math.Ceiling(p);
                    dx = q - Math.Floor(q);
                    p = Math.Ceiling(p);
                    if (p == image.GetLength(0))
                    {
                        p--;
                    }

                    q = Math.Floor(q);
                    if (q == width)
                    {
                        q--;
                    }

                    for (int m = -1; m < 3; m++)
                    {
                        for (int n = -1; n < 3; n++)
                        {
                            if (p == 0)
                            {
                                p++;
                            }
                            else if (p == height - 1)
                            {
                                p -= 2;
                            }
                            else if (p == height - 2)
                            {
                                p--;
                            }

                            if (q == 0)
                            {
                                q++;
                            }
                            else if (q == width - 1)
                            {
                                q -= 2;
                            }
                            else if (q == width - 2)
                            {
                                q--;
                            }

                            double smallbSplineM = instance.BicubicInterpolationFunction((double)m - dx, mode);
                            double smallbSplineN = instance.BicubicInterpolationFunction((dy - (double)n), mode);
                            sumR += image[(int)p + m, (int)q + n].R * smallbSplineM * smallbSplineN;
                            sumG += image[(int)p + m, (int)q + n].G * smallbSplineM * smallbSplineN;
                            sumB += image[(int)p + m, (int)q + n].B * smallbSplineM * smallbSplineN;
                            sumDenom += smallbSplineM * smallbSplineN;
                        }
                    }

                    newImage[i, j].R = (byte)Math.Round((sumR / sumDenom));
                    newImage[i, j].G = (byte)Math.Round((sumG / sumDenom));
                    newImage[i, j].B = (byte)Math.Round((sumB / sumDenom));
                }
            }
        }

        /// <summary>
        /// Bis the line resize threading.
        /// </summary>
        /// <param name="semaphoreObj">The semaphore obj.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="myimage">The myimage.</param>
        /// <param name="newIamge">The new iamge.</param>
        private void BiLineResizeThreading(Semaphore semaphoreObj, int minHeight, int maxHeight, ImageStructure[,] myimage, ImageStructure[,] newIamge)
        {
            semaphoreObj.Release();
            double temp = GetWidth(myimage);
            double smallwRatio = Convert.ToDouble(temp / GetWidth(newIamge)); // calc ratio between new width and old width  factorX
            temp = GetHeight(myimage);
            double heightRatio = Convert.ToDouble(temp / GetHeight(newIamge));   // calc ratio between new Height and old Height factorY

            int oldX, oldY;
            int x1, x2, y1, y2;
            ImageStructure p1, p2, p3, p4, z1, z2, newPixel;
            double smallxFraction, smallyFraction;

            for (int i = minHeight; i < maxHeight; i++)
            {
                for (int j = 0; j < GetWidth(newIamge); j++)
                {
                    oldX = Convert.ToInt32(j * smallwRatio);
                    oldY = Convert.ToInt32(i * heightRatio);
                    x1 = Convert.ToInt32(oldX);
                    x2 = x1 + 1;

                    y1 = Convert.ToInt32(oldY);
                    y2 = y1 + 1;
                    if (x2 >= GetWidth(myimage) || y2 >= GetHeight(myimage))
                    {
                        if (oldY == GetHeight(myimage))
                        {
                            oldY--;
                        }
                        else if (oldX == GetWidth(myimage))
                        {
                            oldX--;
                        }

                        newIamge[i, j] = myimage[oldY, oldX];
                        break;
                    }

                    p1.R = myimage[y1, x1].R;
                    p1.G = myimage[y1, x1].G;
                    p1.B = myimage[y1, x1].B;

                    p2.R = myimage[y2, x1].R;
                    p2.B = myimage[y2, x1].B;
                    p2.G = myimage[y2, x1].G;

                    p3.R = myimage[y1, x2].R;
                    p3.B = myimage[y1, x2].B;
                    p3.G = myimage[y1, x2].G;

                    p4.R = myimage[y2, x2].R;
                    p4.B = myimage[y2, x2].B;
                    p4.G = myimage[y2, x2].G;

                    smallxFraction = oldX - x1;
                    smallyFraction = oldY - y1;

                    z1.R = Convert.ToByte((p1.R * (1 - smallxFraction)) + (p2.R * smallxFraction));
                    z1.B = Convert.ToByte((p1.B * (1 - smallxFraction)) + (p2.B * smallxFraction));
                    z1.G = Convert.ToByte((p1.G * (1 - smallxFraction)) + (p2.G * smallxFraction));

                    z2.R = Convert.ToByte((p3.R * (1 - smallxFraction)) + (p4.R * smallxFraction));
                    z2.B = Convert.ToByte((p3.B * (1 - smallxFraction)) + (p4.B * smallxFraction));
                    z2.G = Convert.ToByte((p3.G * (1 - smallxFraction)) + (p4.G * smallxFraction));

                    newPixel.R = Convert.ToByte((z1.R * (1 - smallyFraction)) + (z2.R * smallyFraction));
                    newPixel.B = Convert.ToByte((z1.B * (1 - smallyFraction)) + (z2.B * smallyFraction));
                    newPixel.G = Convert.ToByte((z1.G * (1 - smallyFraction)) + (z2.G * smallyFraction));

                    newIamge[i, j].R = newPixel.R;
                    newIamge[i, j].G = newPixel.G;
                    newIamge[i, j].B = newPixel.B;
                }
            }
        }
    }
}
